﻿using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Media;
using Creventive.Wpf.ShellFactory.Logging;

namespace Creventive.Wpf.ShellFactory.Workspace.Primitives
{
    /// <summary>
    ///   Provides a base class for most workspaces.
    /// </summary>
    public abstract class Workspace : Control, IWorkspace
    {
        #region Static Fields/Constants

        /// <summary>
        /// The dependency property backing field for the ContainerStyle property
        /// </summary>
        public static readonly DependencyProperty ContainerStyleProperty = DependencyProperty.Register("ContainerStyle", typeof(Style), typeof(Workspace), new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.None, (s, e) => ((Workspace)s).OnContainerStyleChanged((Style)e.OldValue, (Style)e.NewValue)));

        /// <summary>
        /// The dependency property backing field for the AllowClose property
        /// </summary>
        public static readonly DependencyProperty AllowCloseProperty = DependencyProperty.Register("AllowClose", typeof(bool), typeof(Workspace), new FrameworkPropertyMetadata(true, FrameworkPropertyMetadataOptions.None, (s, e) => ((Workspace)s).OnAllowCloseChanged((bool)e.OldValue, (bool)e.NewValue)));

        /// <summary>
        /// The dependency property backing field for the AllowOpen property
        /// </summary>
        public static readonly DependencyProperty AllowOpenProperty = DependencyProperty.Register("AllowOpen", typeof(bool), typeof(Workspace), new FrameworkPropertyMetadata(true, FrameworkPropertyMetadataOptions.None, (s, e) => ((Workspace)s).OnAllowOpenChanged((bool)e.OldValue, (bool)e.NewValue)));

        #endregion

        private ObservableCollection<ApplicationViewContext> viewContexts;

        static Workspace()
        {
            FocusableProperty.OverrideMetadata(typeof(Workspace), new FrameworkPropertyMetadata(false));
            IsTabStopProperty.OverrideMetadata(typeof(Workspace), new FrameworkPropertyMetadata(false));
        }

        /// <summary>
        ///   Initializes a new instance of the <see cref = "Workspace" /> class.
        /// </summary>
        protected Workspace()
        {
            this.CommandBindings.Add(new CommandBinding(ApplicationCommands.Close, this.ExecuteClose, this.CanExecuteClose));
            this.CommandBindings.Add(new CommandBinding(ApplicationCommands.Open, this.ExecuteOpen, this.CanExecuteOpen));
            this.viewContexts = new ObservableCollection<ApplicationViewContext>();
        }

        /// <summary>
        ///   Gets or sets a value indicating whether this workspace allows views to close via the Close command.
        /// </summary>
        /// <value><c>true</c> if views are allowed to close via the Close command; otherwise, <c>false</c>.</value>
        public bool AllowClose
        {
            get { return (bool)this.GetValue(AllowCloseProperty); }
            set { this.SetValue(AllowCloseProperty, value); }
        }

        /// <summary>
        ///   Gets or sets a value indicating whether this workspace allows views to be opened via the Open command.
        /// </summary>
        /// <value><c>true</c> if views can be opened via the Open command; otherwise, <c>false</c>.</value>
        public bool AllowOpen
        {
            get { return (bool)this.GetValue(AllowOpenProperty); }
            set { this.SetValue(AllowOpenProperty, value); }
        }

        /// <summary>
        ///   Gets or sets the style to use on the workspace items.
        /// </summary>
        /// <value>The container style.</value>
        public Style ContainerStyle
        {
            get { return (Style)this.GetValue(ContainerStyleProperty); }
            set { this.SetValue(ContainerStyleProperty, value); }
        }

        /// <summary>
        ///   Called when the AllowOpen property has changed.
        /// </summary>
        /// <param name = "oldValue">The old value.</param>
        /// <param name = "newValue">The new value.</param>
        protected virtual void OnAllowOpenChanged(bool oldValue, bool newValue)
        {}

        /// <summary>
        ///   Called when the AllowClose property has changed.
        /// </summary>
        /// <param name = "oldValue">Old value.</param>
        /// <param name = "newValue">New value.</param>
        protected virtual void OnAllowCloseChanged(bool oldValue, bool newValue)
        {
            CommandManager.InvalidateRequerySuggested();
        }

        private void CanExecuteOpen(object sender, CanExecuteRoutedEventArgs e)
        {
            var logger = Logger.Default;
            if (!(e.Parameter is ApplicationView || e.Parameter is IApplicationViewSource))
                logger.Warning("Called the Open command query without designating which view to open");
            else
            {
                bool result;
                if (e.Source is ApplicationView)
                    result = this.CanOpen((ApplicationView)e.Parameter);
                else
                    result = this.CanOpen((IApplicationViewSource)e.Parameter);
                e.ContinueRouting = false;
                e.Handled = true;
                e.CanExecute = result;
            }
        }

        private void ExecuteOpen(object sender, ExecutedRoutedEventArgs e)
        {
            var logger = Logger.Default;
            if (!(e.Parameter is ApplicationView || e.Parameter is IApplicationViewSource))
                logger.Warning("Called the Open command without designating which view to open");
            else
            {
                if (e.Source is ApplicationView)
                    this.Open((ApplicationView)e.Parameter);
                else
                    this.Open((IApplicationViewSource)e.Parameter);
                e.Handled = true;
            }
        }

        private void CanExecuteClose(object sender, CanExecuteRoutedEventArgs e)
        {
            var logger = Logger.Default;
            ApplicationView source = e.Parameter as ApplicationView;
            if (source == null)
                logger.Warning("Called the Close command query without designating which view to close");
            if (source != null && this.ContainsView(source))
            {
                if (this.AllowClose)
                    e.CanExecute = this.Close(source, ApplicationViewClosingMode.QueryOnly);
                else
                    e.CanExecute = false;
                e.ContinueRouting = false;
                e.Handled = true;
            }
            else
                e.ContinueRouting = true;
        }

        private void ExecuteClose(object sender, ExecutedRoutedEventArgs e)
        {
            var logger = Logger.Default;
            ApplicationView source = (e.Parameter as ApplicationView);
            if (source == null)
                logger.Warning("Called the Close command without designating which view to close");
            if (source != null && this.ContainsView(source))
            {
                this.Close(source);
                e.Handled = true;
            }
        }

        /// <summary>
        ///   Determines whether the specified application view exists within this workspace.
        /// </summary>
        /// <param name = "applicationView">The application view.</param>
        /// <returns>
        ///   <c>true</c> if the specified application view contains view; otherwise, <c>false</c>.
        /// </returns>
        public abstract bool ContainsView(ApplicationView applicationView);

        /// <summary>
        ///   Searches for all workspaces in a given root element and closes all views presented in them according to the given flags.
        /// </summary>
        /// <param name = "rootElement">The root element.</param>
        /// <param name = "flags">The flags.</param>
        /// <returns></returns>
        public static bool CloseAllViews(DependencyObject rootElement, ApplicationViewClosingMode flags = ApplicationViewClosingMode.None)
        {
            var workspaces = FindRootWorkspaces(rootElement);

            switch (flags)
            {
                case ApplicationViewClosingMode.None:
                {
                    if (workspaces.Any(ws => !ws.CloseAll(ApplicationViewClosingMode.QueryOnly)))
                        return false;
                    flags = ApplicationViewClosingMode.NoQuery;
                    goto case ApplicationViewClosingMode.NoQuery;
                }

                case ApplicationViewClosingMode.QueryOnly:
                case ApplicationViewClosingMode.NoQuery:
                {
                    foreach (var workspace in workspaces)
                    {
                        if (!workspace.CloseAll(flags))
                            return false;
                    }
                    return true;
                }

                default:
                    throw new ArgumentOutOfRangeException("flags");
            }
        }

        private static IEnumerable<IWorkspace> FindRootWorkspaces(DependencyObject rootElement)
        {
            Stack<DependencyObject> stack = new Stack<DependencyObject>();
            stack.Push(rootElement);
            while (stack.Count > 0)
            {
                var element = stack.Pop();
                if (element is IWorkspace)
                    yield return (IWorkspace)element;
                else
                {
                    for (int i = 0; i < VisualTreeHelper.GetChildrenCount(element); i++)
                        stack.Push(VisualTreeHelper.GetChild(element, i));
                }
            }
        }

        /// <summary>
        ///   Creates and initializes a workspace item for a given view.
        /// </summary>
        /// <param name = "view">The view.</param>
        /// <returns></returns>
        protected virtual WorkspaceItem CreateItem(ApplicationView view)
        {
            WorkspaceItem item = this.CreateNewItem();
            item.BeginInit();
            this.InitializeItem(item, view);
            item.EndInit();
            return item;
        }

        /// <summary>
        ///   Initializes the given item using the given view.
        /// </summary>
        /// <param name = "item">The item.</param>
        /// <param name = "view">The view.</param>
        protected virtual void InitializeItem(WorkspaceItem item, ApplicationView view)
        {
            if (this.ContainerStyle != null)
                item.Style = this.ContainerStyle;
            item.Workspace = this;
            item.ApplicationView = view;
        }

        /// <summary>
        ///   Creates a new workspace empty item.
        /// </summary>
        /// <returns></returns>
        protected virtual WorkspaceItem CreateNewItem()
        {
            return new WorkspaceItem();
        }

        /// <summary>
        ///   Destroys the given workspace item.
        /// </summary>
        /// <param name = "item">The item.</param>
        protected virtual void DestroyItem(WorkspaceItem item)
        {
            if (item == null)
                throw new ArgumentNullException("item");
            item.ApplicationView = null;
            item.Workspace = null;
        }

        /// <summary>
        ///   Called when the ContainerStyle property has changed.
        /// </summary>
        /// <param name = "oldValue">The old value.</param>
        /// <param name = "newValue">The new value.</param>
        protected abstract void OnContainerStyleChanged(Style oldValue, Style newValue);

        /// <summary>
        ///   Gets the loaded view context for the given application view.
        /// </summary>
        /// <param name = "view">The view.</param>
        /// <returns></returns>
        public ApplicationViewContext GetViewContext(ApplicationView view)
        {
            return this.viewContexts.SingleOrDefault(c => ReferenceEquals(c.View, view));
        }

        /// <summary>
        ///   Gets the loaded view context for the given source.
        /// </summary>
        /// <param name = "viewSource">The view source.</param>
        /// <returns></returns>
        public ApplicationViewContext GetViewContext(IApplicationViewSource viewSource)
        {
            return this.viewContexts.SingleOrDefault(c => ReferenceEquals(c.Source, viewSource));
        }

        /// <summary>
        ///   Determines whether this workspace can open the specified view source.
        /// </summary>
        /// <param name = "viewSource">The view source.</param>
        /// <returns>
        ///   <c>true</c> if this workspace can open the specified view source; otherwise, <c>false</c>.
        /// </returns>
        public virtual bool CanOpen(IApplicationViewSource viewSource)
        {
            return true;
        }

        /// <summary>
        ///   Determines whether this workspace can open the specified view.
        /// </summary>
        /// <param name = "view">The view.</param>
        /// <returns>
        ///   <c>true</c> if this workspace can open the specified view; otherwise, <c>false</c>.
        /// </returns>
        public virtual bool CanOpen(ApplicationView view)
        {
            return true;
        }

        /// <summary>
        ///   Opens the specified view source.
        /// </summary>
        /// <param name = "viewSource">The view source.</param>
        public virtual void Open(IApplicationViewSource viewSource)
        {
            var context = GetViewContext(viewSource);
            if (context == null)
            {
                context = new ApplicationViewContext(viewSource);
                this.OpenCore(context);
            }
            else
                this.Activate(context.View);
        }

        /// <summary>
        ///   Opens the specified view.
        /// </summary>
        /// <param name = "view">The view.</param>
        public virtual void Open(ApplicationView view)
        {
            var context = GetViewContext(view);
            if (context == null)
            {
                context = new ApplicationViewContext(view);
                this.OpenCore(context);
            }
            else
                this.Activate(context.View);
        }

        /// <summary>
        ///   Activates the specified view.
        /// </summary>
        /// <param name = "view">The view.</param>
        public virtual bool Activate(ApplicationView view)
        {
            var context = GetViewContext(view);
            if (context != null)
            {
                this.OnActivateView(view);
                return true;
            }
            else
                return false;
        }

        private void OpenCore(ApplicationViewContext viewContext)
        {
            this.viewContexts.Add(viewContext);
            this.OnAddView(viewContext.View);
        }

        /// <summary>
        ///   Closes all views presented by this workspace according to the given flags.
        /// </summary>
        /// <param name = "flags">The flags.</param>
        /// <returns><c>true</c> if all views were successfully closed.</returns>
        public virtual bool CloseAll(ApplicationViewClosingMode flags = ApplicationViewClosingMode.None)
        {
            return WorkspaceHelper.CloseViews(this.viewContexts.Select(v => v.View), flags, this.KillView);
        }

        /// <summary>
        ///   Closes the view created by the specified source according to the given flags.
        /// </summary>
        /// <param name = "source">The source.</param>
        /// <param name = "flags">The flags.</param>
        /// <returns><c>true</c> if the view was successfully closed.</returns>
        public virtual bool Close(IApplicationViewSource source, ApplicationViewClosingMode flags = ApplicationViewClosingMode.None)
        {
            var context = GetViewContext(source);
            if (context != null && context.IsLoaded)
                return Close(context.View, flags);
            else
                return true;
        }

        /// <summary>
        ///   Closes the specified view according to the given flags.
        /// </summary>
        /// <param name = "view">The view to close.</param>
        /// <param name = "flags">The flags.</param>
        /// <returns><c>true</c> if the view was successfully closed.</returns>
        public virtual bool Close(ApplicationView view, ApplicationViewClosingMode flags = ApplicationViewClosingMode.None)
        {
            return WorkspaceHelper.CloseViews(new[] { view }, flags, this.KillView);
        }

        private void KillView(ApplicationView view, object custom)
        {
            var context = GetViewContext(view);
            if (context != null)
            {
                this.OnRemoveView(view);
                this.viewContexts.Remove(context);
            }
        }

        /// <summary>
        ///   Called when a view should be activated.
        /// </summary>
        /// <param name = "view">The view.</param>
        protected abstract void OnActivateView(ApplicationView view);

        /// <summary>
        ///   Called when a new view has been added to the workspace.
        /// </summary>
        /// <param name = "view">The view.</param>
        protected abstract void OnAddView(ApplicationView view);

        /// <summary>
        ///   Called when a view is to be removed from the workspace.
        /// </summary>
        /// <param name = "view">The view.</param>
        protected abstract void OnRemoveView(ApplicationView view);
    }
}